/* lmap.c */

/****************************************************************************

Prints out information about a disc's disc structures:

   *lmap <drive_num> <ADFS|SCSI>

Filing system defaults to ADFS, drive number to 4.

Assumes drive numbers 0 to 3 are E-format floppies.

Prints out:

  Disc record (hex and English)

****************************************************************************/


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "swis.h"


/***************************************************************************/

typedef struct {
  char log2secsize;
  char secspertrack;
  char heads;
  char density;
  char idlen;
  char log2bpmb;
  char skew;
  char bootoption;
  char lowsector;
  char nzones;
  char zone_spare[2];
  unsigned root;
  unsigned disc_size;
  char disc_id[2];
  char disc_name[10];
  unsigned disc_type;
} DiscRecord;

/***************************************************************************/

static int drive_num;

static int FS;
static int FS_DiscOp;

enum {
  FS_ADFS = 0,
  FS_SCSI = 1
};

/***************************************************************************/

static int log2secsize;
static int sector_size;
static int id_len;
static int alloc_size;
static int num_zones;
static int zone_spare;
static int log2bpmb;

static int ids_per_zone;
static int max_num_frags;
static int secs_per_alloc;

/***************************************************************************/

#define  FRAG_ID_IN(x)  (((x)>>8)&0x7fff)
#define  SECTOR_IN(x)   ((x)&0xff)

/***************************************************************************/

typedef struct NameItem {
  struct NameItem *prev;
  char name[1];
} NameItem;

#define  MAX_PATH_SIZE  500
static char *path_name;

typedef struct FragItem {
  struct FragItem *next;
  NameItem *name;
  int type;
  int sector;
  int length;
} FragItem;

static FragItem** frag;

/***************************************************************************/

static char *dir_name;

typedef struct {
  unsigned load;
  unsigned exec;
  int length;
  unsigned attributes;
  int object_type;
  unsigned sin;
  char timestamp[5];
  char name[12];
} DirEntry;

enum {
  FILE_OBJECT = 1,
  DIR_OBJECT = 2,
  IMAGE_OBJECT = 3
};

static DirEntry dir_buff;

/***************************************************************************/

#define  ALL  1
  /* 1 => print out details of each fragment
     0 => just a summary for each zone
  */

static int total_disc_space;
static int allocated_disc_space;
static int free_disc_space;

static int num_empty_files;
static int num_nonempty_files;
static int num_directories;

static int num_free_frags;
static int num_shared_frags;
static int num_unshared_frags;
static int num_frag_id_1;
  /* a count of the number of fragments with frag_id = 1; this should be one
     greater than the number of areas with defects */

static int num_bad_frags;
  /* a count of the number of fragments which appear not to conform to the
     rules for a shared or an unshared fragment - should be zero always */

static int filecore_lost_space;
  /* a count of the number of sectors inside fragments which FileCore
     believes to be allocated, but to which no directory entry refers */

static int num_shared_multi_frags;
  /* a count of the number of times that a shared disc object has extra
     fragments */

static int num_shared_objs;
static int num_single_objs;
static int num_multi_objs;

static int sector_wastage;
  /* a count of the number of bytes unused in the last sector of a file */

static int alloc_unit_wastage;
  /* a count of the number of sectors wasted because an unshared fragment
     has to be a multiple of the allocation unit in size */

static int other_wastage;
  /* a count of any other sectors wasted in an unshared fragment */

static int num_other_wastage;
  /* a count of the number of such situations found */

static int shared_space_available;
  /* a count of the number of sectors available in shared fragments that
     are not full */

static int num_shared_space_available;
  /* a count of the number of such situations found */

static int num_non_compact;
  /* a count of the number of shared fragments that are not compact */

static int max_shared_size;
  /* the maximum size of any shared fragment */

static int min_unshared_size;
  /* the minimum size of any unshared fragment */

/***************************************************************************/

static char *alloc_next;
static char *alloc_end;

#define  ROUND_UP_TO_WORD(x)  (((x)+3)&~3)
#define  SEG_SIZE  10000

/***************************************************************************/

static int mask1[] = { 0xff, 0xfe, 0xfc, 0xf8, 0xf0, 0xe0, 0xc0, 0x80 };
static int mask2[] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 };

/***************************************************************************/

static void print_hex(char *buff, int n)

  /* prints buff[0] to buff[n-1] byte-by-byte in hex, 16 entries per line */

{
  int i, j, lim;

  for (j=0; j<n; j+=16)
  {
    lim = j+16;
    if (lim>n) lim=n;
    printf("%04x:", j);
    for (i=j; i<lim; i++)
      printf(" %02x", buff[i]);
    printf("\n");
  }

  return;
}

/***************************************************************************/

static void *check_alloc(int size)

  /* allocates storage - fatal error if insufficient available */

{
  void *res;

  if ((res = malloc(size))==NULL)
  {
    printf("*** Insufficient memory available\n");
    exit(0);
  }

  return res;
}



/***************************************************************************/

static int next_one_bit(char *v, int offset)

  /* v is a bit array, and offset indexes an entry in this array.

     The result is the index of the first 'one' bit found whose index is
      greater than or equal to offset.
  */

{
  int byte = offset >> 3;
  int bit = offset & 0x7;

  if ((v[byte] & mask1[bit]) == 0)
  {
    do
    {
      byte++;
    } while (v[byte] == 0);
    bit = 0;
  }

  while ((v[byte] & mask2[bit]) == 0)
    bit++;

  return ((byte << 3) + bit);
}

/***************************************************************************/

static int get_bits(char *v, int offset, int len)

  /* v is a bit array, and offset indexes an entry in this array.

     The result is the integer value of the bit-string whose least
      significant bit is v[offset], and whose most significant bit
      is v[offset+len-1].
  */

{
  int byte1 = offset >> 3;
  int bit1 = offset & 0x7;
  int byte2 = (offset+len) >> 3;
  int bit2 = (offset+len) & 0x7;
  int x, y;
  int bits;

 /* pick up first part */
  y = v[byte1] & mask1[bit1];

 /* does this contain all there is? */
  if (byte1 == byte2)
  {
    y &= ~mask1[bit2];
    y >>= bit1;
    return y;
  }

  y >>= bit1;
  bits = 8-bit1;

 /* now add any complete bytes to y */
  do
  {
    byte1++;
    if (byte1 == byte2) break;
    y |= v[byte1] << bits;
    bits += 8;
  } while (TRUE);

 /* and any final part */
  x = v[byte2] & ~mask1[bit2];
  y |= x << bits;

  return y;
}

/***************************************************************************/

static char *name_in(NameItem *item)

  /* reconstructs a path name */

{
  char *p = path_name + MAX_PATH_SIZE;

 /* we're working backwards - start with the terminator! */
  *p = 0;

  do
  {
    int n = strlen(item->name);

    p -= n;
    memcpy(p, item->name, n);

    p -= 1;
    *p = '.';

    item = item->prev;
  } while (item != NULL);

  return p+1;
}

/***************************************************************************/

static void print_map_entries(char *map_block, int zone)

  /* lists the map entries in the given map block:

       'map_block' addresses a buffer containing the map block for
         zone 'zone'
  */

{
  int map_entry, map_end, free;

  int min_frag_id = zone*ids_per_zone;
  int max_frag_id = min_frag_id + ids_per_zone - 1;

  int num_alloc = 0, num_free = 0;
  int tot_alloc = 0, tot_free = 0;

 /* set map_entry to offset of first bit of allocation bytes */
  map_entry = 4*8;    /* allow for map block header */
  if (zone == 0)      /* and for disc record if it's the first zone */
    map_entry += 60*8;

 /* set map_end to one beyond offset of final bit of allocation bytes */
  map_end = (sector_size+4)*8 - zone_spare;
  if (zone == num_zones-1 && zone != 0)
   /* last zone map block goes right to the end - unless its an E-format
      floppy ...  */
    map_end = sector_size*8;

 /* set free to offset of first free map entry */
  free = get_bits(map_block, 8, id_len) + 8;

 /* title line */
  printf("Entries in map block for zone %d:\n\n", zone);
  printf("  id     size    size   size start len  name\n");
  printf("        (bits)    (k) (secs)  sec   type\n\n");

 /* process each map entry in turn */
  while (map_entry < map_end)
  {
    int frag_id = get_bits(map_block, map_entry, id_len);
    int next_entry = next_one_bit(map_block, map_entry+id_len)+1;
    int size = next_entry - map_entry;
    BOOL is_free = (map_entry == free);

    if (is_free)           /* is this the next free map entry? */
    {
      free += frag_id;
#if ALL
      printf("(free) ");
#endif

      num_free_frags++;
      num_free++;
      tot_free += size;
    }
    else
    {
#if ALL
      printf("%6d", frag_id);
      if (frag_id < min_frag_id)
        printf("<");
      else
      if (frag_id > max_frag_id)
        printf(">");
      else
        printf(" ");
#endif

      if (!(zone==num_zones-1 && frag_id==1)) /* don't count the bit of */
      {                                       /* disc that isn't there */
        num_alloc++;
        tot_alloc += size;
      }
    }
#if ALL
    printf(" %5d %7.1fk %5d", size, (float)(size<<log2bpmb)/1024.0,
               (size<<log2bpmb)>>log2secsize);
#endif

   /* little more to do if the fragment is:  */
    if ( is_free ||
          /* free, */
         frag_id==2 && zone==0 && (num_zones>1 || drive_num>=4) ||
          /* contains the boot block, */
         frag_id==1
          /* or contains the bad sectors */
       )
    {
     /* count fragments for defects */
      if (frag_id == 1)
        num_frag_id_1++;
#if ALL
      printf("\n");
#endif
    }
    else       /* list details of objects in this fragment, if any */
    {
      FragItem *p;
      int frag_size = (size<<log2bpmb)>>log2secsize;
      BOOL unshared_fragment;


     /* first establish whether it is a shared or unshared fragment, and
        check consistency of FragItems */

      p = frag[frag_id];

     /* find first active FragItem */
      while (p != NULL && p->length == 0)
        p = p->next;

      if (p == NULL)      /* should not happen */
      {
        printf("*** nothing in this one\n");
        num_bad_frags++;
        filecore_lost_space += frag_size;

        map_entry = next_entry;
        continue;
      }

      unshared_fragment = (p->sector <= 0);

      if (unshared_fragment && p->next != NULL)  /* should not happen */
      {
        printf("*** more than one object in an unshared fragment\n");
        num_bad_frags++;

        map_entry = next_entry;
        continue;
      }

      if (!unshared_fragment)
      {
        BOOL ok = TRUE;

        while (p != NULL)
        {
          if (p->sector <= 0)  /* should not happen */
          {
            ok = FALSE;
            break;
          }

          p = p->next;
        }

        if (!ok)
        {
          printf("*** shared fragment includes unshared object\n");
          num_bad_frags++;

          map_entry = next_entry;
          continue;
        }
      }

      if (unshared_fragment)
        num_unshared_frags++;
      else
        num_shared_frags++;


    /* process the FragItem corresponding to an unshared fragment */

      if (unshared_fragment)
      {
        int length;
        int section;

        if (frag_size < min_unshared_size && frag_id > 2)
          min_unshared_size = frag_size;

        p = frag[frag_id];
        length = p-> length;
        section = (1 - p->sector);

        if (section == 1)
        {
          if (length > frag_size)
            num_multi_objs++;
          else
            num_single_objs++;
        }

       /* is this the last fragment for the object? - if not, update the
           FragItem to refer to the remainder of object */
        if (length > frag_size)
        {
          p->length -= frag_size;
          p->sector--;
          length = frag_size;
        }
        else
          p->length = 0;    /* mark FragItem as finished with */

       /* note any wasted space here */
        {
          int lost_space = frag_size - length;
          BOOL abnormal = (lost_space >= secs_per_alloc);

          if (abnormal)
          {
            other_wastage += lost_space;
            num_other_wastage++;
          }
          else
            alloc_unit_wastage += lost_space;
#if ALL
          printf("  %3s(%2d)", (abnormal)?"***":"   ", section);
          printf(" %c %s\n",
                  (p->type == DIR_OBJECT)?'D':'F',
                  name_in(p->name));
#endif
        }
      }


      else

    /* or process each remaining FragItem for a shared fragment */
      {
        BOOL first_time = TRUE;
        BOOL compact = TRUE;

        if (frag_size > max_shared_size && frag_id > 2)
          max_shared_size = frag_size;

       /* find first FragItem not yet completely processed */
        p = frag[frag_id];
        while (p->length == 0)
          p = p->next;

        while (p != NULL)
        {
          int length = p->length;
          BOOL spanner = FALSE;
#if ALL
          if (!first_time)
            printf("                            ");
#endif

          if (length < 0)
           /* a partially processed FragItem - ie a shared object which spans
              two fragments in a multi-fragment disc object */
          {
            spanner = TRUE;
            length = -length;
          }
          else
            num_shared_objs++;

          if (p->next != NULL &&
              p->sector + length != p->next->sector)
            compact = FALSE;

          if (length > frag_size)   /* this FragItem must become a spanner */
          {
            num_shared_multi_frags++;
            spanner = TRUE;
            p->length = -(frag_size - length);
            p->sector += frag_size;
            length = frag_size;
          }
          else
            p->length = 0;
#if ALL
          printf(" %c%3d %3d", spanner?'S':' ', p->sector-1, length);

          printf(" %c %s\n",
                  (p->type == DIR_OBJECT)?'D':'F',
                  name_in(p->name));
#endif

          frag_size -= length;
          if (frag_size == 0)      /* no space for more shared objects */
            break;

          first_time = FALSE;
          p = p->next;
        }

       /* note any space remaining in a shared fragment */
        if (frag_size > 0)
        {
          num_shared_space_available++;
          shared_space_available += frag_size;
        }

        if (!compact)
          num_non_compact++;
      }
    }

    map_entry = next_entry;
  }


  if (map_entry != map_end)
    printf("**** Map block not formatted correctly\n");

  if (num_alloc > 0)
    printf("\n  %4d allocated fragments occupy %7.1fk, average size = %6.2fk\n",
                  num_alloc, (float)(tot_alloc*alloc_size)/1024.0,
                  (float)(tot_alloc*alloc_size)/(1024.0*num_alloc));
  else
    printf("\n  No allocated fragments present\n");

  if (num_free > 0)
    printf("  %4d      free fragments occupy %7.1fk, average size = %6.2fk\n",
                  num_free, (float)(tot_free*alloc_size)/1024.0,
                  (float)(tot_free*alloc_size)/(1024.0*num_free));
  else
    printf("  No free fragments present\n");

  printf("  Total size = %d bits = %7.1fk\n\n", tot_free + tot_alloc,
               (float)((tot_free+tot_alloc)*alloc_size)/1024.0);

 /* accumulate space usage statistics */
  total_disc_space += ((tot_free+tot_alloc)<<log2bpmb)>>log2secsize;
  allocated_disc_space += (tot_alloc<<log2bpmb)>>log2secsize;
  free_disc_space += (tot_free<<log2bpmb)>>log2secsize;

  return;
}

/***************************************************************************/

static char *local_alloc(int size)

  /* allocates from the allocation segment defined by alloc_next and
     alloc_end; malloc's a new segment if necessary
  */

{
  if (alloc_next+size > alloc_end)
  {
    alloc_next = (char*)check_alloc(SEG_SIZE);
    alloc_end = alloc_next + SEG_SIZE;
  }

  alloc_next += size;

  return (alloc_next-size);
}

/***************************************************************************/
static int num_frag_items = 0;
static FragItem *new_frag_item(NameItem *name_item, int type,
                                 int sector, int length)

  /* allocates and initialises a new FragItem; note that the length supplied
      is in bytes, whereas the value stored in the FragItem is in sectors.

     Maintains a cumulative count of the number of bytes "wasted" because
     of the sector granularity for files.
  */

{
  int size = sizeof(FragItem);
  FragItem *item = (FragItem*)local_alloc(size);
num_frag_items++;
  item->name = name_item;
  item->type = type;
  item->sector = sector;
  item->length = ((length-1)>>log2secsize)+1;
  item->next = NULL;

 /* how many bytes are wasted? */
  if (type == FILE_OBJECT)
  {
    int mask = sector_size - 1;      /* all ones! */
    int last_part = length & mask;

    if (last_part > 0)
      sector_wastage += (sector_size - last_part);
  }

  return item;
}

/***************************************************************************/

static NameItem *new_name_item(char *name)

  /* allocates and initialises a new NameItem containing the given name */

{
  int size = sizeof(NameItem*) + ROUND_UP_TO_WORD(strlen(name)+1);
  NameItem *item = (NameItem*)local_alloc(size);

  strcpy(item->name, name);
  item->prev = NULL;

  return item;
}

/***************************************************************************/

static void process_dir(NameItem *name_item)

  /* On entry:

       dir_name  - contains the name of a directory

       name_item - addresses the NameItem for that directory

     Each object within the directory is (recursively) processed:
        - a NameItem is created for the object, and linked to its parent
        - a FragItem is created for the object, and inserted into the list
           associated with the corresponding fragment

     On exit:

       dir_name again contains the name of the original directory
  */

{
  int index = 0;
  int res;

  num_directories++;

  do
  {
    _swi(OS_GBPB, I0|I1|I2|I3|I4|I5|I6|O3|O4,
           11,                /* OS_GBPB 11 - read directory entries */
           (int)dir_name,     /* from this directory */
           (int)&dir_buff,    /* into this buffer */
           1,                 /* one at a time */
           index,             /* starting with this one */
           sizeof(dir_buff),  /* length of buffer */
           0,                 /* read all of them */
           &res,              /* set to number read - or -1 if none */
           &index             /* identifies next entry to read */
        );

    if (res == 1)             /* I expect this is always the case, but ... */
    {
      NameItem *new_name;
      FragItem *frag_item;
      int frag_id;
      int sector;
      int type;
      int length;

     /* create NameItem for object, and link it to its parent */
      new_name = new_name_item(dir_buff.name);
      new_name->prev = name_item;

     /* extract info from object ready for FragItem creation */
      frag_id = FRAG_ID_IN(dir_buff.sin);
      sector = SECTOR_IN(dir_buff.sin);
      type = dir_buff.object_type;
      length = dir_buff.length;

     /* treat image objects as files */
      if (type == IMAGE_OBJECT)
        type = FILE_OBJECT;

      if (type == FILE_OBJECT)
      {
        if (length == 0)
          num_empty_files++;
        else
          num_nonempty_files++;
      }

     /* Note:

         Zero length files have a sin = 0x00000001, and so will all be
         attached to the frag[0] list.
     */

     /* create FragItem */
      frag_item = new_frag_item(new_name, type, sector, length);

     /* insert into the correct place in the list attached to the fragment */
      {
        FragItem **p = frag + frag_id;
        FragItem *q;

        while (TRUE)
        {
          q = *p;
          if (q == NULL || q->sector >= sector)
            break;
          p = &(q->next);
        }

        *p = frag_item;
        frag_item->next = q;
      }

      if (type == DIR_OBJECT)
      {
        int len = strlen(dir_name);

        dir_name[len] = '.';
        strcpy(dir_name+len+1, dir_buff.name);

        process_dir(new_name);  /* recurse */

        dir_name[len] = 0;      /* restore previous directory name */
      }
    }

  } while (index >= 0);       /* -1 => no more entries to read */

  return;
}

/***************************************************************************/

static void print_disc_record(DiscRecord *rec)

  /* prints contents of disc record in buffer */

{
  int i;
  char *buff = (char*)rec;

  printf("Disc record:\n\n");
  for (i=0; i<22; i++)
    printf(" %02x", buff[i]);
  printf("\n");
  for (i=22; i<36; i++)
    printf(" %02x", buff[i]);
  printf("\n\n");

  printf("  sector size = %d bytes\n", 1 << (rec->log2secsize));
  printf("  sectors per track = %d\n", rec->secspertrack);
  printf("  heads = %d\n", rec->heads);
  printf("  density = %d\n", rec->density);
  printf("  fragment_id length = %d bits\n", rec->idlen);
  printf("  allocation unit size = %d bytes\n", 1 << (rec->log2bpmb));
  printf("  skew = %d\n", rec->skew);
  printf("  boot option = %d\n", rec->bootoption);
  printf("  lowsector = %d\n", rec->lowsector);
  printf("  number of zones = %d\n", rec->nzones);
  printf("  spare bits between zone maps = %d\n",
              (rec->zone_spare[1])*256 + (rec->zone_spare[0]));
  printf("  disc address of root directory = &%08x = %d\n",
              rec->root, rec->root);
  printf("  disc size = %d bytes = %.2f Mbytes\n", rec->disc_size,
             ((float)rec->disc_size)/1048576.0);
  printf("  disc cycle id = &%02x%02x\n", rec->disc_id[1], rec->disc_id[0]);
  {
    char name[11];

    strncpy(name, rec->disc_name, 10);
    name[10] = 0;  /* just in case */

    printf("  disc name = %10s\n", name);
  }
  printf("  disc file type = &%08x\n", rec->disc_type);

  printf("\n");

  return;
}

/***************************************************************************/

static void read_boot_block(int drive_num, char *buff)

  /* read disc boot block for given drive number (0->7) into buffer */

{
  _swi(FS_DiscOp, I1|I2|I3|I4,
        1,                           /* read sectors */
        (drive_num << 29) + 0xc00,
        (int)buff,
        512
      );

  return;
}

/***************************************************************************/

int main(int argc, char *argv[])

{
  char *buff;
  DiscRecord *discrec;
  unsigned map_addr;
  int i;

 /* initialise allocation pointers */
  alloc_next = alloc_end = NULL;

 /* determine drive number of interest */
  if (argc < 2)
    drive_num = 4;
  else
    drive_num = atoi(argv[1]);

 /* and what kind of FileCore filing system */
  FS = FS_ADFS;  /* default */
  if (argc >= 3)
  {
    if (strcmp(argv[2], "SCSI") == 0 || strcmp(argv[2], "scsi") == 0)
      FS = FS_SCSI;
  }

 /* set up SWIs accordingly */
  switch (FS)
  {
    case FS_ADFS:
      FS_DiscOp = ADFS_DiscOp;
      break;

    case FS_SCSI:
      FS_DiscOp = SCSIFS_DiscOp;
      break;
  }

 /* determine address of map */
  if (drive_num < 4)      /* floppy */
  {
    map_addr = 0;         /* assume E-format floppy for now */
    sector_size = 1024;
  }
  else                    /* hard disc */
  {
   /* read boot block, and hence base disc record */
    buff = check_alloc(512);
    read_boot_block(drive_num, buff);
    discrec = (DiscRecord*)(buff + 0x1c0);
     /* disc record at offset &1c0 */

   /* determine address of map */
    {
      int sec_size = 1 << (discrec->log2secsize);
      int alloc_size = 1 << (discrec->log2bpmb);
      int num_spare = (discrec->zone_spare[1]*256 + discrec->zone_spare[0]);
      int zone_size = (sec_size*8 - num_spare)*alloc_size;
      int zone0_size = zone_size - (60*8*alloc_size);
      int map_zone = (discrec->nzones)/2;

      map_addr = zone0_size + (map_zone-1)*zone_size;
      sector_size = sec_size;
    }

    free(buff);
  }
  printf("\nMap address = %d (%08x)\n", map_addr, map_addr);

  buff = check_alloc(sector_size);

 /* read first map block */
  _swi(FS_DiscOp, I1|I2|I3|I4,
        1,                           /* read sectors */
        (drive_num << 29) + map_addr,
        (int)buff,
        sector_size
      );

 /* locate real disc record */
  discrec = (DiscRecord*)(buff+4);
  print_disc_record(discrec);

 /* establish some important values */
  log2secsize = discrec->log2secsize;
  sector_size = 1 << log2secsize;
  id_len = discrec->idlen;
  alloc_size = 1 << discrec->log2bpmb;
  num_zones = discrec->nzones;
  zone_spare = (discrec->zone_spare[1])*256 + (discrec->zone_spare[0]);
  log2bpmb = discrec->log2bpmb;

 /* calculate the number of sectors in the minimum possible incremental
    allocation ("granularity") */
  if (alloc_size < sector_size)
    secs_per_alloc = 1;
  else
    secs_per_alloc = (1<<log2bpmb)>>log2secsize;

  ids_per_zone = (sector_size*8 - zone_spare)/(id_len+1);
  max_num_frags = num_zones*ids_per_zone;

  printf("  ids_per_zone = %d\n", ids_per_zone);
  printf("  max_num_frags = %d\n\n", max_num_frags);

 /* allocate and initialise space for fragment information array */
  frag = (FragItem**)check_alloc(max_num_frags*sizeof(FragItem*));
  for (i=0; i<max_num_frags; i++)
    frag[i] = NULL;

 /* allocate space for object names */
  dir_name = (char*)check_alloc(MAX_PATH_SIZE+1);
  path_name = (char*)check_alloc(MAX_PATH_SIZE+1);

 /* initialise statistics variables */
  total_disc_space = 0;
  allocated_disc_space = 0;
  free_disc_space = 0;

  num_empty_files = 0;
  num_nonempty_files = 0;
  num_directories = 0;

  num_free_frags = 0;
  num_shared_frags = 0;
  num_unshared_frags = 0;
  num_frag_id_1 = 0;
  num_bad_frags = 0;
  filecore_lost_space = 0;
  num_shared_multi_frags = 0;

  num_shared_objs = 0;
  num_single_objs = 0;
  num_multi_objs = 0;

  sector_wastage = 0;
  alloc_unit_wastage = 0;
  other_wastage = 0;
  num_other_wastage = 0;
  shared_space_available = 0;
  num_shared_space_available = 0;
  num_non_compact = 0;
  max_shared_size = 0;
  min_unshared_size = 0x7fffffff;

 /* construct name of root directory for this filing system and drive */
  sprintf(dir_name, "%s::%d.$", (FS==FS_ADFS)?"adfs":"scsi", drive_num);

 /* process root directory itself, and then traverse dir and file tree */
  {
    NameItem *root_name = new_name_item("$");
    int root_frag_id = FRAG_ID_IN(discrec->root);
    int root_sector = SECTOR_IN(discrec->root);
    int length;

   /* find size of root directory */
    _swi(OS_File, I0|I1|O4,
           20,                /*  OS_File 20 */
           dir_name,
           &length
        );

   /* add entry for root directory to fragment array */
    frag[root_frag_id] = new_frag_item(root_name, DIR_OBJECT,
                                          root_sector, length);

   /* and process the tree ... */
    process_dir(root_name);
  }

 /* now process entries in each map block */
  {
    int zone;

    for (zone=0; zone<num_zones; zone++)
    {
     /* read corresponding map block into buffer */
      _swi(FS_DiscOp, I1|I2|I3|I4,
            1,                           /* read sectors */
            (drive_num << 29) + map_addr + zone*sector_size,
            (int)buff,
            sector_size
          );

     /* and print it out */
      print_map_entries(buff, zone);
    }
  }

 /* list any unprocessed FragItems for non-zero frag_ids */
  {
    BOOL first_time = TRUE;

    for (i=1; i<max_num_frags; i++)
    {
      FragItem *p = frag[i];

      while (p != NULL)
      {
        if (p->length != 0)
        {
          if (first_time)
          {
            printf("The following objects claim to be in fragments which are absent:\n");
            first_time = FALSE;
          }
          printf(" %5d %3d %3d %c %s\n", i, p->sector, p->length,
                  (p->type == DIR_OBJECT)?'D':'F',
                  name_in(p->name));
        }
        p = p->next;
      }
    }
  }
printf("num_frag_items = %d\n", num_frag_items);
 /* print out statistics */
  printf("  total_disc_space = %d sectors\n", total_disc_space);
  printf("  allocated_disc_space = %d\n", allocated_disc_space);
  printf("  free_disc_space = %d\n", free_disc_space);

  printf("  num_empty_files = %d\n", num_empty_files);
  printf("  num_nonempty_files = %d\n", num_nonempty_files);
  printf("  num_directories = %d\n", num_directories);
  printf("  num_free_frags = %d\n", num_free_frags);
  printf("  num_shared_frags = %d\n", num_shared_frags);
  printf("  num_unshared_frags = %d\n", num_unshared_frags);
  printf("  num_frag_id_1 = %d\n", num_frag_id_1);
  printf("  num_bad_frags = %d\n", num_bad_frags);
  printf("  filecore_lost_space = %d\n", filecore_lost_space);
  printf("  num_shared_multi_frags = %d\n", num_shared_multi_frags);
  printf("  num_shared_objs = %d\n", num_shared_objs);
  printf("  num_single_objs = %d\n", num_single_objs);
  printf("  num_multi_objs = %d\n", num_multi_objs);
  printf("  sector_wastage = %d bytes,\n", sector_wastage);
  printf("    averaging at %d bytes per non-empty file\n",
                 sector_wastage/num_nonempty_files);
  printf("  alloc_unit_wastage = %d sectors\n", alloc_unit_wastage);
  printf("  other_wastage = %d sectors\n", other_wastage);
  printf("  num_other_wastage = %d\n", num_other_wastage);
  printf("  shared_space_available = %d\n", shared_space_available);
  printf("  num_shared_space_available = %d\n", num_shared_space_available);
  printf("  num_non_compact = %d\n", num_non_compact);
  printf("  max_shared_size = %d\n", max_shared_size);
  printf("  min_unshared_size = %d\n", min_unshared_size);

  return 0;
}

/***************************************************************************/
